기계학습 (1116) 11주차

순환신경망
Author

김보람

Published

November 16, 2022

RNN(2)– AbAcAd예제, GPU실험 // LSTM– abcabC, abcdabcD, LSTM의 계산과정, LSTM은 왜 강한가?

강의영상

https://youtube.com/playlist?list=PLQqh36zP38-zckrqil6HUKenNLBeiwn1T

import

import torch
import numpy as np
import matplotlib.pyplot as plt

Define some funtions

def f(txt,mapping):
    return [mapping[key] for key in txt] 
sig = torch.nn.Sigmoid()
soft = torch.nn.Softmax(dim=1)
tanh = torch.nn.Tanh()

Exam4: AbAcAd (2)

data

- 기존의 정리방식

txt = list('AbAcAd')*100
txt[:10]
['A', 'b', 'A', 'c', 'A', 'd', 'A', 'b', 'A', 'c']
txt_x = txt[:-1]
txt_y = txt[1:]
txt_x[:5],txt_y[:5]
(['A', 'b', 'A', 'c', 'A'], ['b', 'A', 'c', 'A', 'd'])
x = torch.nn.functional.one_hot(torch.tensor(f(txt_x,{'A':0,'b':1,'c':2,'d':3}))).float()
y = torch.nn.functional.one_hot(torch.tensor(f(txt_y,{'A':0,'b':1,'c':2,'d':3}))).float()
x,y
(tensor([[1., 0., 0., 0.],
         [0., 1., 0., 0.],
         [1., 0., 0., 0.],
         ...,
         [1., 0., 0., 0.],
         [0., 0., 1., 0.],
         [1., 0., 0., 0.]]), tensor([[0., 1., 0., 0.],
         [1., 0., 0., 0.],
         [0., 0., 1., 0.],
         ...,
         [0., 0., 1., 0.],
         [1., 0., 0., 0.],
         [0., 0., 0., 1.]]))

순환신경망 구현1 (손으로 직접구현) – 리뷰

(1) 숙성담당 네트워크

class rNNCell(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.i2h = torch.nn.Linear(4,2) 
        self.h2h = torch.nn.Linear(2,2) 
        self.tanh = torch.nn.Tanh()
    def forward(self,x,hidden):
        hidden = self.tanh(self.i2h(x)+self.h2h(hidden))
        return hidden
torch.manual_seed(43052)
rnncell = rNNCell() # 숙성담당 네트워크 

(2) 조리담당 네트워크

torch.manual_seed(43052)
cook = torch.nn.Linear(2,4) 

(3) 손실함수, 옵티마이저 설계

loss_fn = torch.nn.CrossEntropyLoss() 
optimizr = torch.optim.Adam(list(rnncell.parameters())+list(cook.parameters()))

(4) 학습 (6분정도 걸림)

T = len(x) 
for epoc in range(5000): 
    ## 1~2
    loss = 0 
    ht = torch.zeros(1,2) 
    for t in range(T):
        xt,yt = x[[t]], y[[t]]
        ht = rnncell(xt,ht) 
        ot = cook(ht) 
        loss = loss + loss_fn(ot,yt) 
    ## 3 
    loss.backward()
    ## 4 
    optimizr.step()
    optimizr.zero_grad()

(5) 시각화

T = len(x) 
hidden = torch.zeros(T,2) # 599년치 h를 담을 변수 
_water = torch.zeros(1,2) # 맹물 
hidden[[0]] = rnncell(x[[0]],_water) 
for t in range(1,T):
    hidden[[t]] = rnncell(x[[t]],hidden[[t-1]]) 
yhat = soft(cook(hidden))
yhat
tensor([[1.6522e-02, 6.2036e-01, 1.0433e-01, 2.5879e-01],
        [9.9965e-01, 6.5788e-05, 1.8450e-05, 2.6785e-04],
        [7.6673e-05, 1.9704e-01, 8.0201e-01, 8.7218e-04],
        ...,
        [7.4634e-05, 1.9501e-01, 8.0407e-01, 8.4751e-04],
        [9.4785e-01, 7.4711e-03, 6.1182e-04, 4.4064e-02],
        [3.6306e-02, 1.2466e-01, 2.8862e-03, 8.3615e-01]],
       grad_fn=<SoftmaxBackward0>)
plt.matshow(yhat.data[-15:],cmap='bwr')
<matplotlib.image.AxesImage at 0x7f09e935fa50>

순환신경망 구현2 (with RNNCell, hidden node 2)

ref: https://pytorch.org/docs/stable/generated/torch.nn.RNNCell.html

구현1과 같은 초기값 (확인용)

(1) 숙성네트워크

torch.manual_seed(43052)
_rnncell = rNNCell() # 숙성담당 네트워크 
rnncell = torch.nn.RNNCell(4,2)   # 4=x , 2=h

rNNCell() 는 사실 torch.nn.RNNCell()와 같은 동작을 하도록 설계를 하였음. 같은동작을 하는지 확인하기 위해서 동일한 초기상태에서 rNNCell()에 의하여 학습된 결과와 torch.nn.RNNCell()에 의하여 학습된 결과를 비교해보자.

rnncell.weight_ih.data = _rnncell.i2h.weight.data
rnncell.bias_ih.data = _rnncell.i2h.bias.data
rnncell.weight_hh.data = _rnncell.h2h.weight.data
rnncell.bias_hh.data = _rnncell.h2h.bias.data

# 초기상태를 똑같이! 앞에서 손으로 직접구현한 것과 일치하는지 확인하기 위해서.

(2) 조리네트워크

torch.manual_seed(43052)
cook = torch.nn.Linear(2,4) # 숙성된 2차원의 단어를 다시 4차원으로 바꿔줘야지 나중에 softmax취할 수 있음

(3) 손실함수와 옵티마이저

loss_fn = torch.nn.CrossEntropyLoss()
optimizr = torch.optim.Adam(list(rnncell.parameters())+list(cook.parameters()))

(4) 학습

T = len(x) 
for epoc in range(5000):
    ## 1~2
    loss = 0 
    ht = torch.zeros(1,2) 
    for t in range(T):
        xt,yt = x[[t]], y[[t]]
        ht = rnncell(xt,ht)
        ot = cook(ht)
        loss = loss + loss_fn(ot,yt) 
    ## 3 
    loss.backward()
    ## 4 
    optimizr.step()
    optimizr.zero_grad()

(5) 시각화

hidden = torch.zeros(T,2) 
# t=0 
_water = torch.zeros(1,2)
hidden[[0]] = rnncell(x[[0]],_water)
# t=1~T 
for t in range(1,T):
    hidden[[t]] = rnncell(x[[t]],hidden[[t-1]])
yhat = soft(cook(hidden))
yhat
tensor([[1.6522e-02, 6.2036e-01, 1.0433e-01, 2.5879e-01],
        [9.9965e-01, 6.5788e-05, 1.8450e-05, 2.6785e-04],
        [7.6673e-05, 1.9704e-01, 8.0201e-01, 8.7218e-04],
        ...,
        [7.4634e-05, 1.9501e-01, 8.0407e-01, 8.4751e-04],
        [9.4785e-01, 7.4711e-03, 6.1182e-04, 4.4064e-02],
        [3.6306e-02, 1.2466e-01, 2.8862e-03, 8.3615e-01]],
       grad_fn=<SoftmaxBackward0>)
plt.matshow(yhat[:15].data,cmap='bwr')
<matplotlib.image.AxesImage at 0x7f09e0352f90>

plt.matshow(yhat[-15:].data,cmap='bwr')
<matplotlib.image.AxesImage at 0x7f09c75ffd90>

새로운 초기값

(1) 숙성네트워크

torch.manual_seed(43052)
torch.nn.RNNCell(4,2)
RNNCell(4, 2)

(2) 조리네트워크

torch.manual_seed(43052)
cook = torch.nn.Linear(2,4) # 숙성된 2차원의 단어를 다시 4차원으로 바꿔줘야지 나중에 softmax취할 수 있음

(3) 손실함수와 옵티마이저

loss_fn = torch.nn.CrossEntropyLoss()
optimizr = torch.optim.Adam(list(rnncell.parameters())+list(cook.parameters()))

(4) 학습

T = len(x) 
for epoc in range(5000):
    ## 1~2
    loss = 0 
    ht = torch.zeros(1,2) 
    for t in range(T):
        xt,yt = x[[t]], y[[t]]
        ht = rnncell(xt,ht)
        ot = cook(ht)
        loss = loss + loss_fn(ot,yt) 
    ## 3 
    loss.backward()
    ## 4 
    optimizr.step()
    optimizr.zero_grad()

(5) 시각화

yhat을 저장한 적이 없는데? x -> h -> outfut -> yhat 이렇게 되야하는데.. 히든레이어에 출력이 T시점에만 저장되어 있고 1부터 599에 해당되는 히든레이어가 저장이 안되어있는 네트워크만 저장된 상태 시각화를 위해 hidden레이어를 재정의 해야함

hidden = torch.zeros(T,2)
#맹물을 만들자
_water=torch.zeros(1,2)
#t=0
hidden[[0]] = rnncell(x[[0]],_water)

#t=1~T
for t in range(1,T):
  hidden[[t]] = rnncell(x[[t]],hidden[[t-1]])

yhat = soft(cook(hidden)) 
plt.matshow(yhat[:15].data,cmap='bwr')

순환신경망 구현3 (with RNN, hidden node 2) – 성공

(예비학습)

- 네트워크학습이후 yhat을 구하려면 번거로웠음

hidden = torch.zeros(T,2) 
_water = torch.zeros(1,2)
hidden[[0]] = rnncell(x[[0]],_water)
for t in range(1,T):
    hidden[[t]] = rnncell(x[[t]],hidden[[t-1]])
yhat = soft(cook(hidden))

- 이렇게 하면 쉽게(?) 구할 수 있음

rnn = torch.nn.RNN(4,2)
rnn.weight_hh_l0.data = rnncell.weight_hh.data    # 2x2 매트릭스
rnn.weight_ih_l0.data = rnncell.weight_ih.data
rnn.bias_hh_l0.data = rnncell.bias_hh.data
rnn.bias_ih_l0.data = rnncell.bias_ih.data

- rnn(x,_water)의 결과는 (1) 599년치 간장 (2) 599번째 간장 이다

_water = torch.zeros(1,2)
rnn(x,_water), hidden 
# 두개의 값이 같다! 

# x: tupple.. 
((tensor([[-0.9912, -0.9117],
          [ 0.0698, -1.0000],
          [-0.9927, -0.9682],
          ...,
          [-0.9935, -0.9315],
          [ 0.5777, -1.0000],
          [-0.9960, -0.0109]], grad_fn=<SqueezeBackward1>),
  tensor([[-0.9960, -0.0109]], grad_fn=<SqueezeBackward1>)),
 tensor([[-0.9912, -0.9117],
         [ 0.0698, -1.0000],
         [-0.9927, -0.9682],
         ...,
         [-0.9935, -0.9315],
         [ 0.5777, -1.0000],
         [-0.9960, -0.0109]], grad_fn=<IndexPutBackward0>))
soft(cook(rnn(x,_water)[0]))
tensor([[1.9725e-02, 1.5469e-03, 8.2766e-01, 1.5106e-01],
        [9.1875e-01, 1.6513e-04, 6.7702e-02, 1.3384e-02],
        [2.0031e-02, 1.0660e-03, 8.5248e-01, 1.2642e-01],
        ...,
        [1.9640e-02, 1.3568e-03, 8.3705e-01, 1.4196e-01],
        [9.9564e-01, 1.3114e-05, 3.5069e-03, 8.4108e-04],
        [3.5473e-03, 1.5670e-01, 1.4102e-01, 6.9873e-01]],
       grad_fn=<SoftmaxBackward0>)

(예비학습결론) torch.nn.RNN(4,2)는 torch.nn.RNNCell(4,2)의 batch 버전이다. (for문이 포함된 버전이다)


torch.nn.RNN(4,2)를 이용하여 구현하자.

(1) 숙성네트워크

선언

rnn = torch.nn.RNN(4,2)

가중치초기화

torch.manual_seed(43052)
_rnncell = torch.nn.RNNCell(4,2)
rnn.weight_hh_l0.data = _rnncell.weight_hh.data 
rnn.weight_ih_l0.data = _rnncell.weight_ih.data
rnn.bias_hh_l0.data = _rnncell.bias_hh.data
rnn.bias_ih_l0.data = _rnncell.bias_ih.data

(2) 조리네트워크

torch.manual_seed(43052)
cook = torch.nn.Linear(2,4) 

(3) 손실함수와 옵티마이저

loss_fn = torch.nn.CrossEntropyLoss()
optimizr = torch.optim.Adam(list(rnn.parameters())+list(cook.parameters()))

(4) 학습

# 맹물을 넣어주기 위한 세팅
_water = torch.zeros(1,2) 

for epoc in range(5000):
    ## 1  hT: 그냥 써논거 
    hidden,hT = rnn(x,_water)
    output = cook(hidden) #output이 배치로 나오게 된다.
    ## 2 
    loss = loss_fn(output,y)
    ## 3 
    loss.backward()
    ## 4 
    optimizr.step()   #갱신
    optimizr.zero_grad()  #초기화

(5) 시각화1: yhat

yhat = soft(output)
plt.matshow(yhat.data[:15],cmap='bwr')
<matplotlib.image.AxesImage at 0x7fe67c231310>

  • 처음은 좀 틀렸음 ㅎㅎ
plt.matshow(yhat.data[-15:],cmap='bwr')
<matplotlib.image.AxesImage at 0x7fe67c1c5d90>

  • 뒤에는 잘맞음

실전팁: _water 대신에 hT를 대입 (사실 큰 차이는 없음)

# hT에 값이 있음
# rnn(x,hT)[0][:6] 값과 hidden[:6] 값을 비교해보면 비슷함 
rnn(x[:6],_water),rnn(x[:6],hT)
((tensor([[-0.9912, -0.9117],
          [ 0.0698, -1.0000],
          [-0.9927, -0.9682],
          [ 0.5761, -1.0000],
          [-0.9960, -0.0173],
          [ 0.9960, -1.0000]], grad_fn=<SqueezeBackward1>),
  tensor([[ 0.9960, -1.0000]], grad_fn=<SqueezeBackward1>)),
 (tensor([[-0.9713, -1.0000],
          [ 0.0535, -1.0000],
          [-0.9925, -0.9720],
          [ 0.5759, -1.0000],
          [-0.9960, -0.0180],
          [ 0.9960, -1.0000]], grad_fn=<SqueezeBackward1>),
  tensor([[ 0.9960, -1.0000]], grad_fn=<SqueezeBackward1>)))

(6) 시각화2: hidden, yhat

combinded = torch.concat([hidden,yhat],axis=1)
plt.matshow(combinded[-15:].data,cmap='bwr')
<matplotlib.image.AxesImage at 0x7fe67c13b7d0>

  • 히든노드의 해석이 어려움.

순환신경망 구현4 (with RNN, hidden node 3) – 성공

(1) 숙성네트워크~ (2) 조리네트워크

torch.manual_seed(2) #1 
rnn = torch.nn.RNN(4,3) 
cook = torch.nn.Linear(3,4) 

(3) 손실함수와 옵티마이저

loss_fn = torch.nn.CrossEntropyLoss()
optimizr = torch.optim.Adam(list(rnn.parameters())+list(cook.parameters()))

(4) 학습

_water = torch.zeros(1,3) 
for epoc in range(5000):
    ## 1
    hidden,hT = rnn(x,_water) 
    output = cook(hidden) 
    ## 2 
    loss = loss_fn(output,y) 
    ## 3 
    loss.backward()
    ## 4 
    optimizr.step()
    optimizr.zero_grad()

(5) 시각화1: yhat

yhat = soft(output)
plt.matshow(yhat[-15:].data,cmap='bwr')
<matplotlib.image.AxesImage at 0x7fe67c04f550>

(6) 시각화2: hidden, yhat

combinded = torch.concat([hidden,yhat],axis=1)
plt.matshow(combinded[-15:].data,cmap='bwr')
<matplotlib.image.AxesImage at 0x7fe6747ba910>

  • 세번째 히든노드 = 대소문자를 구분
  • 1,2 히든노드 = bcd를 구분

GPU 실험

20000 len + 20 hidden nodes

cpu

x = torch.randn([20000,4]) 
y = torch.randn([20000,4]) 
rnn = torch.nn.RNN(4,20) 
linr = torch.nn.Linear(20,4) 
optimizr = torch.optim.Adam(list(rnn.parameters())+list(linr.parameters()))
loss_fn = torch.nn.MSELoss() 
t1 = time.time()
for epoc in range(100):
    ## 1 
    _water = torch.zeros(1,20)
    hidden, hT = rnn(x,_water) 
    yhat = linr(hidden) 
    ## 2 
    loss = loss_fn(yhat,y) 
    ## 3
    loss.backward()   # 역전파를 하는 곳이 시간을 제일 많이 잡아먹는다. 
    ## 4 
    optimizr.step()
    optimizr.zero_grad()
t2 = time.time()
t2 - t1 
93.01761960983276

gpu

x = torch.randn([20000,4]).to("cuda:0")
y = torch.randn([20000,4]).to("cuda:0")
rnn = torch.nn.RNN(4,20).to("cuda:0")
linr = torch.nn.Linear(20,4).to("cuda:0")
optimizr = torch.optim.Adam(list(rnn.parameters())+list(linr.parameters()))
loss_fn = torch.nn.MSELoss() 
t1 = time.time()
for epoc in range(100):
    ## 1 
    _water = torch.zeros(1,20).to("cuda:0")
    hidden, hT = rnn(x,_water) 
    yhat = linr(hidden) 
    ## 2 
    loss = loss_fn(yhat,y) 
    ## 3
    loss.backward() 
    ## 4 
    optimizr.step()
    optimizr.zero_grad()
t2 = time.time()
t2 - t1 
3.2665085792541504
  • 왜 빠른지?
# for문이 중첩이 되어있는 형태인데, hidden에서 cpu에서는 그래서 오래걸리는 거구... 근데 이것보다는 역전파 하는곳 때문에 더 오래걸린다! 

20000 len + 20 hidden nodes + 역전파주석처리

cpu

x = torch.randn([20000,4]) 
y = torch.randn([20000,4]) 
rnn = torch.nn.RNN(4,20) 
linr = torch.nn.Linear(20,4) 
optimizr = torch.optim.Adam(list(rnn.parameters())+list(linr.parameters()))
loss_fn = torch.nn.MSELoss() 
t1 = time.time()
for epoc in range(100):
    ## 1 
    _water = torch.zeros(1,20)
    hidden, hT = rnn(x,_water) 
    yhat = linr(hidden) 
    ## 2 
    loss = loss_fn(yhat,y) 
    ## 3
    #loss.backward() 
    ## 4 
    optimizr.step()
    optimizr.zero_grad()
t2 = time.time()
t2 - t1 
18.851768255233765

gpu (역전파주석처리)

x = torch.randn([20000,4]).to("cuda:0")
y = torch.randn([20000,4]).to("cuda:0")
rnn = torch.nn.RNN(4,20).to("cuda:0")
linr = torch.nn.Linear(20,4).to("cuda:0")
optimizr = torch.optim.Adam(list(rnn.parameters())+list(linr.parameters()))
loss_fn = torch.nn.MSELoss() 
t1 = time.time()
for epoc in range(100):
    ## 1 
    _water = torch.zeros(1,20).to("cuda:0")
    hidden, hT = rnn(x,_water) 
    yhat = linr(hidden) 
    ## 2 
    loss = loss_fn(yhat,y) 
    ## 3
    #loss.backward() 
    ## 4 
    optimizr.step()
    optimizr.zero_grad()
t2 = time.time()
t2 - t1 
1.2901742458343506

2000 len + 20 hidden nodes

cpu

x = torch.randn([2000,4]) 
y = torch.randn([2000,4]) 
rnn = torch.nn.RNN(4,20) 
linr = torch.nn.Linear(20,4) 
optimizr = torch.optim.Adam(list(rnn.parameters())+list(linr.parameters()))
loss_fn = torch.nn.MSELoss() 
t1 = time.time()
for epoc in range(100):
    ## 1 
    _water = torch.zeros(1,20)
    hidden, hT = rnn(x,_water) 
    yhat = linr(hidden) 
    ## 2 
    loss = loss_fn(yhat,y) 
    ## 3
    loss.backward() 
    ## 4 
    optimizr.step()
    optimizr.zero_grad()
t2 = time.time()
t2 - t1 
6.533619165420532

gpu

x = torch.randn([2000,4]).to("cuda:0")
y = torch.randn([2000,4]).to("cuda:0")
rnn = torch.nn.RNN(4,20).to("cuda:0")
linr = torch.nn.Linear(20,4).to("cuda:0")
optimizr = torch.optim.Adam(list(rnn.parameters())+list(linr.parameters()))
loss_fn = torch.nn.MSELoss() 
t1 = time.time()
for epoc in range(100):
    ## 1 
    _water = torch.zeros(1,20).to("cuda:0")
    hidden, hT = rnn(x,_water) 
    yhat = linr(hidden) 
    ## 2 
    loss = loss_fn(yhat,y) 
    ## 3
    loss.backward() 
    ## 4 
    optimizr.step()
    optimizr.zero_grad()
t2 = time.time()
t2 - t1 
0.7532594203948975

2000 len + 20 hidden nodes + 역전파주석처리

cpu

x = torch.randn([2000,4]) 
y = torch.randn([2000,4]) 
rnn = torch.nn.RNN(4,20) 
linr = torch.nn.Linear(20,4) 
optimizr = torch.optim.Adam(list(rnn.parameters())+list(linr.parameters()))
loss_fn = torch.nn.MSELoss() 
t1 = time.time()
for epoc in range(100):
    ## 1 
    _water = torch.zeros(1,20)
    hidden, hT = rnn(x,_water) 
    yhat = linr(hidden) 
    ## 2 
    loss = loss_fn(yhat,y) 
    ## 3
    #loss.backward() 
    ## 4 
    optimizr.step()
    optimizr.zero_grad()
t2 = time.time()
t2 - t1 
1.2477965354919434

gpu

x = torch.randn([2000,4]).to("cuda:0")
y = torch.randn([2000,4]).to("cuda:0")
rnn = torch.nn.RNN(4,20).to("cuda:0")
linr = torch.nn.Linear(20,4).to("cuda:0")
optimizr = torch.optim.Adam(list(rnn.parameters())+list(linr.parameters()))
loss_fn = torch.nn.MSELoss() 
t1 = time.time()
for epoc in range(100):
    ## 1 
    _water = torch.zeros(1,20).to("cuda:0")
    hidden, hT = rnn(x,_water) 
    yhat = linr(hidden) 
    ## 2 
    loss = loss_fn(yhat,y) 
    ## 3
    #loss.backward() 
    ## 4 
    optimizr.step()
    optimizr.zero_grad()
t2 = time.time()
t2 - t1 
0.14130854606628418

2000 len + 5000 hidden nodes

cpu

x = torch.randn([2000,4]) 
y = torch.randn([2000,4]) 
rnn = torch.nn.RNN(4,1000) 
linr = torch.nn.Linear(1000,4) 
optimizr = torch.optim.Adam(list(rnn.parameters())+list(linr.parameters()))
loss_fn = torch.nn.MSELoss() 
t1 = time.time()
for epoc in range(100):
    ## 1 
    _water = torch.zeros(1,1000)
    hidden, hT = rnn(x,_water) 
    yhat = linr(hidden) 
    ## 2 
    loss = loss_fn(yhat,y) 
    ## 3
    loss.backward() 
    ## 4 
    optimizr.step()
    optimizr.zero_grad()
t2 = time.time()
t2 - t1 
58.99820685386658

gpu

x = torch.randn([2000,4]).to("cuda:0")
y = torch.randn([2000,4]).to("cuda:0")
rnn = torch.nn.RNN(4,1000).to("cuda:0")
linr = torch.nn.Linear(1000,4).to("cuda:0")
optimizr = torch.optim.Adam(list(rnn.parameters())+list(linr.parameters()))
loss_fn = torch.nn.MSELoss() 
t1 = time.time()
for epoc in range(100):
    ## 1 
    _water = torch.zeros(1,1000).to("cuda:0")
    hidden, hT = rnn(x,_water) 
    yhat = linr(hidden) 
    ## 2 
    loss = loss_fn(yhat,y) 
    ## 3
    loss.backward() 
    ## 4 
    optimizr.step()
    optimizr.zero_grad()
t2 = time.time()
t2 - t1 
4.7596595287323

2000 len + 5000 hidden nodes + 역전파주석처리

cpu

x = torch.randn([2000,4]) 
y = torch.randn([2000,4]) 
rnn = torch.nn.RNN(4,1000) 
linr = torch.nn.Linear(1000,4) 
optimizr = torch.optim.Adam(list(rnn.parameters())+list(linr.parameters()))
loss_fn = torch.nn.MSELoss() 
t1 = time.time()
for epoc in range(100):
    ## 1 
    _water = torch.zeros(1,1000)
    hidden, hT = rnn(x,_water) 
    yhat = linr(hidden) 
    ## 2 
    loss = loss_fn(yhat,y) 
    ## 3
    #loss.backward() 
    ## 4 
    optimizr.step()
    optimizr.zero_grad()
t2 = time.time()
t2 - t1 
13.163657188415527

gpu

x = torch.randn([2000,4]).to("cuda:0")
y = torch.randn([2000,4]).to("cuda:0")
rnn = torch.nn.RNN(4,1000).to("cuda:0")
linr = torch.nn.Linear(1000,4).to("cuda:0")
optimizr = torch.optim.Adam(list(rnn.parameters())+list(linr.parameters()))
loss_fn = torch.nn.MSELoss() 
t1 = time.time()
for epoc in range(100):
    ## 1 
    _water = torch.zeros(1,1000).to("cuda:0")
    hidden, hT = rnn(x,_water) 
    yhat = linr(hidden) 
    ## 2 
    loss = loss_fn(yhat,y) 
    ## 3
    #loss.backward() 
    ## 4 
    optimizr.step()
    optimizr.zero_grad()
t2 = time.time()
t2 - t1 
2.2989864349365234

실험결과 요약

len # of hidden nodes backward cpu gpu ratio
20000 20 O 93.02 3.26 28.53
20000 20 X 18.85 1.29 14.61
2000 20 O 6.53 0.75 8.70
2000 20 X 1.25 0.14 8.93
2000 1000 O 58.99 4.75 12.41
2000 1000 X 13.16 2.29 5.74

Exam5: abcabC

data

txt = list('abcabC')*100
txt[:8]
['a', 'b', 'c', 'a', 'b', 'C', 'a', 'b']
txt_x = txt[:-1] 
txt_y = txt[1:]
mapping = {'a':0,'b':1,'c':2,'C':3} 
x= torch.nn.functional.one_hot(torch.tensor(f(txt_x,mapping))).float()
y= torch.nn.functional.one_hot(torch.tensor(f(txt_y,mapping))).float()
x = x.to("cuda:0")
y = y.to("cuda:0") 
x.shape
torch.Size([599, 4])

a1, b1, c, a2, b2, C - 보이는 문자수가 a,b,c,C 이므로 4개 - 문맥까지 고려하면 6개(a1, a2와 같이,.)

RNN

torch.manual_seed(43052) 
rnn = torch.nn.RNN(4,3)    # 문맥의 차이 고려가 힘드니까 히든레이어를 3개 정도는 있어야 문맥에 따른걸 생각할 수 있을 거 같다!  
linr = torch.nn.Linear(3,4) 
loss_fn = torch.nn.CrossEntropyLoss()
optimizr = torch.optim.Adam(list(rnn.parameters())+ list(linr.parameters()))
rnn.to("cuda:0") 
linr.to("cuda:0")
Linear(in_features=3, out_features=4, bias=True)

- 3000 epochs

for epoc in range(3000):
    ## 1 
    _water = torch.zeros(1,3).to("cuda:0")
    hidden, hT = rnn(x,_water)
    output = linr(hidden) 
    ## 2 
    loss = loss_fn(output,y) 
    ## 3 
    loss.backward()
    ## 4 
    optimizr.step()
    optimizr.zero_grad()
yhat = soft(output)
combinded  = torch.concat([hidden,yhat],axis=1).data.to("cpu")
plt.matshow(combinded[-6:],cmap='bwr')
<matplotlib.image.AxesImage at 0x7f47e032f890>

- 6000 epochs

for epoc in range(3000):
    ## 1 
    _water = torch.zeros(1,3).to("cuda:0")
    hidden, hT = rnn(x,_water)
    output = linr(hidden) 
    ## 2 
    loss = loss_fn(output,y) 
    ## 3 
    loss.backward()
    ## 4 
    optimizr.step()
    optimizr.zero_grad()
yhat = soft(output)
combinded  = torch.concat([hidden,yhat],axis=1).data.to("cpu")
plt.matshow(combinded[-6:],cmap='bwr')
<matplotlib.image.AxesImage at 0x7f47e1078b90>

- 9000 epochs

for epoc in range(3000):
    ## 1 
    _water = torch.zeros(1,3).to("cuda:0")
    hidden, hT = rnn(x,_water)
    output = linr(hidden) 
    ## 2 
    loss = loss_fn(output,y) 
    ## 3 
    loss.backward()
    ## 4 
    optimizr.step()
    optimizr.zero_grad()
yhat = soft(output)
combinded  = torch.concat([hidden,yhat],axis=1).data.to("cpu")
plt.matshow(combinded[-6:],cmap='bwr')
<matplotlib.image.AxesImage at 0x7f47e0358590>

- 12000 epochs

for epoc in range(3000):
    ## 1 
    _water = torch.zeros(1,3).to("cuda:0")
    hidden, hT = rnn(x,_water)
    output = linr(hidden) 
    ## 2 
    loss = loss_fn(output,y) 
    ## 3 
    loss.backward()
    ## 4 
    optimizr.step()
    optimizr.zero_grad()
yhat = soft(output)
combinded  = torch.concat([hidden,yhat],axis=1).data.to("cpu")
plt.matshow(combinded[-6:],cmap='bwr')
<matplotlib.image.AxesImage at 0x7f47e2de6f10>

- 15000 epochs

for epoc in range(3000):
    ## 1 
    _water = torch.zeros(1,3).to("cuda:0")
    hidden, hT = rnn(x,_water)
    output = linr(hidden) 
    ## 2 
    loss = loss_fn(output,y) 
    ## 3 
    loss.backward()
    ## 4 
    optimizr.step()
    optimizr.zero_grad()
yhat = soft(output)
combinded  = torch.concat([hidden,yhat],axis=1).data.to("cpu")
plt.matshow(combinded[-6:],cmap='bwr')
<matplotlib.image.AxesImage at 0x7f47cc12ae50>

LSTM

- LSTM

torch.manual_seed(43052) 
lstm = torch.nn.LSTM(4,3) #RNN->lstm
linr = torch.nn.Linear(3,4) 
loss_fn = torch.nn.CrossEntropyLoss()
optimizr = torch.optim.Adam(list(lstm.parameters())+ list(linr.parameters()))
lstm.to("cuda:0") 
linr.to("cuda:0")
Linear(in_features=3, out_features=4, bias=True)

- 3000 epochs

for epoc in range(3000):
    ## 1 
    _water = torch.zeros(1,3).to("cuda:0")
    hidden, (hT,cT) = lstm(x,(_water,_water))   # lstm은 물을 두개 넣어줘야 하고 hT랑 cT랑이 나온다..
    output = linr(hidden) 
    ## 2 
    loss = loss_fn(output,y) 
    ## 3 
    loss.backward()
    ## 4 
    optimizr.step()
    optimizr.zero_grad()
yhat = soft(output)
combinded  = torch.concat([hidden,yhat],axis=1).data.to("cpu")
plt.matshow(combinded[-6:],cmap='bwr',vmin=-1,vmax=1)
<matplotlib.image.AxesImage at 0x7f47cc0608d0>

- 6000 epochs

for epoc in range(3000):
    ## 1 
    _water = torch.zeros(1,3).to("cuda:0")
    hidden, (hT,cT) = lstm(x,(_water,_water))
    output = linr(hidden) 
    ## 2 
    loss = loss_fn(output,y) 
    ## 3 
    loss.backward()
    ## 4 
    optimizr.step()
    optimizr.zero_grad()
yhat = soft(output)
combinded  = torch.concat([hidden,yhat],axis=1).data.to("cpu")
plt.matshow(combinded[-6:],cmap='bwr',vmin=-1,vmax=1)
<matplotlib.image.AxesImage at 0x7f47c61dd750>

RNN vs LSTM 성능비교실험

- RNN

fig, ax = plt.subplots(5,5,figsize=(10,10))
for i in range(5):
    for j in range(5):
        rnn = torch.nn.RNN(4,3).to("cuda:0")
        linr = torch.nn.Linear(3,4).to("cuda:0")
        loss_fn = torch.nn.CrossEntropyLoss()
        optimizr = torch.optim.Adam(list(rnn.parameters())+list(linr.parameters()),lr=0.1)
        _water = torch.zeros(1,3).to("cuda:0")
        for epoc in range(3000):
            ## 1
            hidden, hT = rnn(x,_water)
            output = linr(hidden)
            ## 2
            loss = loss_fn(output,y)
            ## 3
            loss.backward()
            ## 4 
            optimizr.step()
            optimizr.zero_grad()
        yhat=soft(output)    
        combind = torch.concat([hidden,yhat],axis=1)
        ax[i][j].matshow(combind.to("cpu").data[-6:],cmap='bwr',vmin=-1,vmax=1)
fig.suptitle(r"$RNN$",size=20)
fig.tight_layout()

- LSTM

fig, ax = plt.subplots(5,5,figsize=(10,10))
for i in range(5):
    for j in range(5):
        lstm = torch.nn.LSTM(4,3).to("cuda:0")
        linr = torch.nn.Linear(3,4).to("cuda:0")
        loss_fn = torch.nn.CrossEntropyLoss()
        optimizr = torch.optim.Adam(list(lstm.parameters())+list(linr.parameters()),lr=0.1)
        _water = torch.zeros(1,3).to("cuda:0")
        for epoc in range(3000):
            ## 1
            hidden, (hT,cT) = lstm(x,(_water,_water))
            output = linr(hidden)
            ## 2
            loss = loss_fn(output,y)
            ## 3
            loss.backward()
            ## 4 
            optimizr.step()
            optimizr.zero_grad()
        yhat=soft(output)    
        combind = torch.concat([hidden,yhat],axis=1)
        ax[i][j].matshow(combind.to("cpu").data[-6:],cmap='bwr',vmin=-1,vmax=1)
fig.suptitle(r"$LSTM$",size=20)
fig.tight_layout()

Exam6: abcdabcD

data

txt = list('abcdabcD')*100
txt[:8]
['a', 'b', 'c', 'd', 'a', 'b', 'c', 'D']
txt_x = txt[:-1]
txt_y = txt[1:]
mapping = {'a':0, 'b':1, 'c':2, 'd':3, 'D':4}
x = torch.nn.functional.one_hot(torch.tensor(f(txt_x,mapping))).float()
y = torch.nn.functional.one_hot(torch.tensor(f(txt_y,mapping))).float()
x=x.to("cuda:0")
y=y.to("cuda:0")

RNN vs LSTM 성능비교실험

- RNN

fig, ax = plt.subplots(5,5,figsize=(10,10))
for i in range(5):
    for j in range(5):
        rnn = torch.nn.RNN(5,4).to("cuda:0")
        linr = torch.nn.Linear(4,5).to("cuda:0")
        loss_fn = torch.nn.CrossEntropyLoss()
        optimizr = torch.optim.Adam(list(rnn.parameters())+list(linr.parameters()),lr=0.1)
        _water = torch.zeros(1,4).to("cuda:0")
        for epoc in range(3000):
            ## 1
            hidden, hT = rnn(x,_water)
            output = linr(hidden)
            ## 2
            loss = loss_fn(output,y)
            ## 3
            loss.backward()
            ## 4 
            optimizr.step()
            optimizr.zero_grad()
        yhat=soft(output)    
        combind = torch.concat([hidden,yhat],axis=1)
        ax[i][j].matshow(combind.to("cpu").data[-8:],cmap='bwr',vmin=-1,vmax=1)
fig.suptitle(r"$RNN$",size=20)
fig.tight_layout()

- LSTM

fig, ax = plt.subplots(5,5,figsize=(10,10))
for i in range(5):
    for j in range(5):
        lstm = torch.nn.LSTM(5,4).to("cuda:0")
        linr = torch.nn.Linear(4,5).to("cuda:0")
        loss_fn = torch.nn.CrossEntropyLoss()
        optimizr = torch.optim.Adam(list(lstm.parameters())+list(linr.parameters()),lr=0.1)
        _water = torch.zeros(1,4).to("cuda:0")
        for epoc in range(3000):
            ## 1
            hidden, (hT,cT) = lstm(x,(_water,_water))
            output = linr(hidden)
            ## 2
            loss = loss_fn(output,y)
            ## 3
            loss.backward()
            ## 4 
            optimizr.step()
            optimizr.zero_grad()
        yhat=soft(output)    
        combind = torch.concat([hidden,yhat],axis=1)
        ax[i][j].matshow(combind.to("cpu").data[-8:],cmap='bwr',vmin=-1,vmax=1)
fig.suptitle(r"$LSTM$",size=20)
fig.tight_layout()

- 관찰1: LSTM이 확실히 장기기억에 강하다.

- 관찰2: LSTM은 hidden에 0이 잘 나온다.

  • 사실 확실히 구분되는 특징을 판별할때는 -1,1 로 히든레이어 값들이 설정되면 명확하다.
  • 히든레이어에 -1~1사이의 값이 나온다면 애매한 판단이 내려지게 된다.
  • 그런데 이 애매한 판단이 어떻게 보면 문맥의 뉘앙스를 이해하는데 더 잘 맞다.
  • 그런데 RNN은 -1,1로 셋팅된 상황에서 -1~1로의 변화가 더디다는 것이 문제임.
# 히든레이어는 하얀색..

LSTM의 계산과정

data: abaB

txt = list('abaB')*100
txt[:5]
['a', 'b', 'a', 'B', 'a']
txt_x = txt[:-1]
txt_y = txt[1:]
mapping = {'a':0, 'b':1, 'B':2}
x = torch.nn.functional.one_hot(torch.tensor(f(txt_x,mapping))).float()
y = torch.nn.functional.one_hot(torch.tensor(f(txt_y,mapping))).float()

1 epoch ver1 (with torch.nn.LSTMCell)

torch.manual_seed(43052) 
lstm_cell = torch.nn.LSTMCell(3,2) #LSTM말고 LSTMCell (LSTM을 batch버전으로..)  # 단어수가 3개니까 3!!! abB, 근데 문맥상 a1,a2,b,B 4개의 문자가 있으니까 히든노드를 2개정도로 잡자.
linr = torch.nn.Linear(2,3)
loss_fn = torch.nn.CrossEntropyLoss() 
optimizr = torch.optim.Adam(list(lstm_cell.parameters())+list(linr.parameters()),lr=0.1)   #lr=학습률
T = len(x) 
for epoc in range(1):
    ht = torch.zeros(1,2)  # 히든노드가 2개니까 차원이 2인 맹물을 만들어주자.
    ct = torch.zeros(1,2)
    loss = 0 
    ## 1~2
    for t in range(T):
        xt,yt = x[[t]], y[[t]]
        ht,ct = lstm_cell(xt,(ht,ct))
        ot = linr(ht) 
        loss = loss + loss_fn(ot,yt)
    loss = loss / T
    ## 3 
    loss.backward()
    ## 4 
    optimizr.step()
    optimizr.zero_grad()
ht,ct 
(tensor([[-0.0406,  0.2505]], grad_fn=<MulBackward0>),
 tensor([[-0.0975,  0.7134]], grad_fn=<AddBackward0>))

1 epoch ver2 (완전 손으로 구현)

t=0 t=1

- lstm_cell 을 이용한 계산 (결과비교용)

torch.manual_seed(43052) 
lstm_cell = torch.nn.LSTMCell(3,2) 
linr = torch.nn.Linear(2,3)
loss_fn = torch.nn.CrossEntropyLoss() 
optimizr = torch.optim.Adam(list(lstm_cell.parameters())+list(linr.parameters()),lr=0.1)
T = len(x) 
for epoc in range(1):
    ht = torch.zeros(1,2)
    ct = torch.zeros(1,2)
    loss = 0 
    ## 1~2
    for t in range(1):
        xt,yt = x[[t]], y[[t]]
        ht,ct = lstm_cell(xt,(ht,ct))
    #     ot = linr(ht) 
    #     loss = loss + loss_fn(ot,yt)
    # loss = loss / T
    # ## 3 
    # loss.backward()
    # ## 4 
    # optimizr.step()
    # optimizr.zero_grad()
ht,ct 
(tensor([[-0.0541,  0.0892]], grad_fn=<MulBackward0>),
 tensor([[-0.1347,  0.2339]], grad_fn=<AddBackward0>))

- 직접계산

ht = torch.zeros(1,2)
ct = torch.zeros(1,2)
_ifgo = xt @ lstm_cell.weight_ih.T + ht @ lstm_cell.weight_hh.T + lstm_cell.bias_ih + lstm_cell.bias_hh
input_gate = sig(_ifgo[:,0:2])
forget_gate = sig(_ifgo[:,2:4])
gt = tanh(_ifgo[:,4:6])
output_gate = sig(_ifgo[:,6:8])
ct = forget_gate * ct + input_gate * gt
ht = output_gate * tanh(ct)
ht,ct
(tensor([[-0.0541,  0.0892]], grad_fn=<MulBackward0>),
 tensor([[-0.1347,  0.2339]], grad_fn=<AddBackward0>))

t=0 t=T

torch.manual_seed(43052) 
lstm_cell = torch.nn.LSTMCell(3,2) 
linr = torch.nn.Linear(2,3)
loss_fn = torch.nn.CrossEntropyLoss() 
optimizr = torch.optim.Adam(list(lstm_cell.parameters())+list(linr.parameters()),lr=0.1)
T = len(x) 
for epoc in range(1):
    ht = torch.zeros(1,2)
    ct = torch.zeros(1,2)
    loss = 0 
    ## 1~2
    for t in range(T):
        xt,yt = x[[t]], y[[t]]
        
        ## lstm_cell step1: calculate _ifgo 
        _ifgo = xt @ lstm_cell.weight_ih.T + ht @ lstm_cell.weight_hh.T + lstm_cell.bias_ih + lstm_cell.bias_hh
        ## lstm_cell step2: decompose _ifgo 
        input_gate = sig(_ifgo[:,0:2])
        forget_gate = sig(_ifgo[:,2:4])
        gt = tanh(_ifgo[:,4:6])
        output_gate = sig(_ifgo[:,6:8])
        ## lstm_cell step3: calculate ht,ct 
        ct = forget_gate * ct + input_gate * gt
        ht = output_gate * tanh(ct)
        
    #     ot = linr(ht) 
    #     loss = loss + loss_fn(ot,yt)
    # loss = loss / T
    # ## 3 
    # loss.backward()
    # ## 4 
    # optimizr.step()
    # optimizr.zero_grad()
ht,ct

#LSMT_Cell로 쉽게 계산한것이랑 값이 같다.
(tensor([[-0.0406,  0.2505]], grad_fn=<MulBackward0>),
 tensor([[-0.0975,  0.7134]], grad_fn=<AddBackward0>))

1 epoch ver3 (with torch.nn.LSTM)

torch.manual_seed(43052) 
lstm_cell = torch.nn.LSTMCell(3,2)
linr = torch.nn.Linear(2,3) 
lstm = torch.nn.LSTM(3,2) 
# batch버전 통해서 확인해보기, 가중치값 덮어씌워보기
lstm.weight_hh_l0.data = lstm_cell.weight_hh.data 
lstm.bias_hh_l0.data = lstm_cell.bias_hh.data 
lstm.weight_ih_l0.data = lstm_cell.weight_ih.data 
lstm.bias_ih_l0.data = lstm_cell.bias_ih.data 
loss_fn = torch.nn.CrossEntropyLoss()
optimizr = torch.optim.Adam(list(lstm.parameters()) + list(linr.parameters()), lr=0.1) 
_water = torch.zeros(1,2) 
for epoc in range(1): 
    ## step1 
    hidden, (ht,ct) = lstm(x,(_water,_water))
    output = linr(hidden)
    # ## step2
    # loss = loss_fn(output,y) 
    # ## step3
    # loss.backward()
    # ## step4 
    # optimizr.step()
    # optimizr.zero_grad() 
ht,ct
(tensor([[-0.0406,  0.2505]], grad_fn=<SqueezeBackward1>),
 tensor([[-0.0975,  0.7134]], grad_fn=<SqueezeBackward1>))

LSTM은 왜 강한가?

data: abaB

txt = list('abaB')*100
txt[:5]
['a', 'b', 'a', 'B', 'a']
n_words = 3
mapping = {'a':0, 'b':1, 'B':2}
txt_x = txt[:-1]
txt_y = txt[1:]
txt_x[:10],txt_y[:10]
(['a', 'b', 'a', 'B', 'a', 'b', 'a', 'B', 'a', 'b'],
 ['b', 'a', 'B', 'a', 'b', 'a', 'B', 'a', 'b', 'a'])
x = torch.nn.functional.one_hot(torch.tensor(f(txt_x,mapping))).float()
y = torch.nn.functional.one_hot(torch.tensor(f(txt_y,mapping))).float()
x,y
(tensor([[1., 0., 0.],
         [0., 1., 0.],
         [1., 0., 0.],
         ...,
         [1., 0., 0.],
         [0., 1., 0.],
         [1., 0., 0.]]),
 tensor([[0., 1., 0.],
         [1., 0., 0.],
         [0., 0., 1.],
         ...,
         [0., 1., 0.],
         [1., 0., 0.],
         [0., 0., 1.]]))

1000 epoch

torch.manual_seed(43052) 
lstm = torch.nn.LSTM(3,2) 
linr = torch.nn.Linear(2,3) 
loss_fn = torch.nn.CrossEntropyLoss() 
optimizr = torch.optim.Adam(list(lstm.parameters())+ list(linr.parameters()),lr=0.1)
_water = torch.zeros(1,2) 
for epoc in range(1000): 
    ## step1 
    hidden, (ht,ct) = lstm(x,(_water,_water))
    output = linr(hidden)
    ## step2
    loss = loss_fn(output,y) 
    ## step3
    loss.backward()
    ## step4 
    optimizr.step()
    optimizr.zero_grad() 

시각화

T = len(x)
input_gate = torch.zeros(T,2)
forget_gate = torch.zeros(T,2)
output_gate = torch.zeros(T,2)
g = torch.zeros(T,2)
cell = torch.zeros(T,2)
h = torch.zeros(T,2)  # 히든노드를 2개로 잡아놨으니까.. 

# 계산식에 의해서 위에 값들이 다 (T,2)형태여야 한다.
# LSTM 계산과정을 다시 따라가면,

for t in range(T): 
    ## 1: calculate _ifgo 
    _ifgo = x[[t]] @ lstm.weight_ih_l0.T + h[[t]] @ lstm.weight_hh_l0.T + lstm.bias_ih_l0 + lstm.bias_hh_l0 
    ## 2: decompose _ifgo 
    input_gate[[t]] = sig(_ifgo[:,0:2])
    forget_gate[[t]] = sig(_ifgo[:,2:4])
    g[[t]] = tanh(_ifgo[:,4:6])
    output_gate[[t]] = sig(_ifgo[:,6:8])
    ## 3: calculate ht,ct 
    cell[[t]] = forget_gate[[t]] * cell[[t]] + input_gate[[t]] * g[[t]]
    h[[t]] = output_gate[[t]] * tanh(cell[[t]])
combinded1 = torch.concat([input_gate,forget_gate,output_gate],axis=1)  # gate끼리 묶어서 시각화
combinded2 = torch.concat([g,cell,h,soft(output)],axis=1)               # 나머지 묶어서 시각화
plt.matshow(combinded1[-8:].data,cmap='bwr',vmin=-1,vmax=1);
plt.xticks(range(combinded1.shape[-1]),labels=['i']*2 + ['f']*2 + ['o']*2);
plt.matshow(combinded2[-8:].data,cmap='bwr',vmin=-1,vmax=1)
plt.xticks(range(combinded2.shape[-1]),labels=['g']*2 + ['c']*2 + ['h']*2 + ['yhat']*3);

  • 상단그림은 게이트의 값들만 시각화, 하단그림은 게이트 이외의 값들을 시각화

시각화의 해석I

plt.matshow(combinded1[-8:].data,cmap='bwr',vmin=-1,vmax=1);
plt.xticks(range(combinded1.shape[-1]),labels=['i']*2 + ['f']*2 + ['o']*2);
NameError: ignored

- input_gate, forget_gate, output_gate는 모두 0~1 사이의 값을 가진다.

파 -1 흰 0 빨 1 이니까 .. xt, ht-1 가지고 sig취해서 i,f,o를 만들었으니 0~1사이의 값을 가진다.

- 이 값들은 각각 모두 gt,ct1,tanh(ct)에 곱해진다. 따라서 input_gate, forget_gate, output_gate 는 gate의 역할로 비유가능하다. (1이면 통과, 0이면 차단)

  • input_gate: gt의 값을 얼만큼 통과시킬지 0~1사이의 숫자로 결정
  • forget_gate: ct1의 값을 얼만큼 통과시킬지 0~1사이의 숫자로 결정
  • output_gate: tanh(ct)의 값을 얼만큼 통과시킬지 0~1사이의 숫자로 결정

시각화의 해석II

plt.matshow(combinded2[-8:].data,cmap='bwr',vmin=-1,vmax=1)
plt.xticks(range(combinded2.shape[-1]),labels=['g']*2 + ['c']*2 + ['h']*2 + ['yhat']*3);

- 결국 gtcthty^ 의 느낌이다. (ht를 계산하기 위해서는 ct가 필요했고 ct를 계산하기 위해서는 ct1gt가 필요했음)

  • ht=tanh(ct)ot
  • ct=ct1ft+gtit

- gt,ct,ht 모두 xt의 정보를 숙성시켜 가지고 있는 느낌이 든다.

- gt 특징: 보통 -1,1 중 하나의 값을 가지도록 학습되어 있다. (마치 RNN의 hidden node처럼!)

  • gt=tanh(xtWig+ht1Whg+big+bhg)

- ct 특징: gt와 매우 비슷하지만 약간 다른값을 가진다. 그래서 gt와는 달리 -1,1 이외의 값도 종종 등장.

print("first row: gt={}, ct={}".format(g[-8].data, cell[-8].data))
print("second row: gt={}, ct={}".format(g[-7].data, cell[-7].data))
#g[-7], cell[-7]
first row: gt=tensor([ 0.9999, -0.9999]), ct=tensor([ 0.9647, -0.9984])
second row: gt=tensor([ 0.9970, -0.9554]), ct=tensor([ 0.3592, -0.9373])

- ht 특징: (1) ct의 느낌이 있음 하지만 약간의 변형이 있음. (2) -1~1 사이에의 값을 훨씬 다양하게 가진다. (tanh때문)

print("first row: gt={}, ct={}, ht={}".format(g[-8].data, cell[-8].data,h[-8].data))
print("second row: gt={}, ct={}, ht={}".format(g[-7].data, cell[-7].data,h[-7].data))
#g[-7], cell[-7]
first row: gt=tensor([ 0.9999, -0.9999]), ct=tensor([ 0.9647, -0.9984]), ht=tensor([ 0.7370, -0.3323])
second row: gt=tensor([ 0.9970, -0.9554]), ct=tensor([ 0.3592, -0.9373]), ht=tensor([ 0.0604, -0.6951])

- 예전의문 해결

  • 실험적으로 살펴보니 LSTM이 RNN보다 장기기억에 유리했음.
  • 그 이유: RRN은 ht의 값이 -1 혹은 1로 결정되는 경우가 많았음. 그러나 경우에 따라서는 ht이 -1~1의 값을 가지는 것이 문맥적 뉘앙스를 포착하기에는 유리한데 LSTM이 이러한 방식으로 학습되는 경우가 많았음.
  • 왜 LSTM의 ht은 -1,1 이외의 값을 쉽게 가질 수 있는가? (1) gate들의 역할 (2) 마지막에 취해지는 tanh 때문

LSTM의 알고리즘 리뷰 I (수식위주)

(step1) calculate ifgo

ifgo=xt[Wii|Wif|Wig|Wio]+ht1[Whi|Whf|Whg|Who]+bias

=[xtWii+ht1Whi | xtWif+ht1Whf | xtWig+ht1Whg | xtWio+ht1Who]+bias

참고: 위의 수식은 아래코드에 해당하는 부분

ifgo = xt @ lstm_cell.weight_ih.T +\
       ht @ lstm_cell.weight_hh.T +\
       lstm_cell.bias_ih + lstm_cell.bias_hh

(step2) decompose ifgo and get it, ft, gt, ot

it=σ(xtWii+ht1Whi+bias)

ft=σ(xtWif+ht1Whf+bias)

gt=tanh(xtWig+ht1Whg+bias)

ot=σ(xtWio+ht1Who+bias)

(step3) calculate ct and ht

ct=itgt+ftct1

ht=tanh(otct)

LSTM의 알고리즘 리뷰 II (느낌위주)

  • 이해 및 암기를 돕기위해서 비유적으로 설명한 챕터입니다..

- 느낌1: RNN이 콩물에서 간장을 한번에 숙성시키는 방법이라면 LSTM은 콩물에서 간장을 3차로 나누어 숙성하는 느낌이다.

  • 콩물: xt
  • 1차숙성: gt
  • 2차숙성: ct
  • 3차숙성: ht

- 느낌2: gt에 대하여

  • 계산방법: xtht1Wig,Whg를 이용해 선형결합하고 tanh를 취한 결과
  • RNN에서 간장을 만들던 그 수식에서 htgt로 바꾼것
  • 크게 2가지의 의미를 가진다 (1) 과거와 현재의 결합 (2) 활성화함수 tanh를 적용

- 느낌3: ct에 대하여 (1)

  • 계산방법: gtct1를 요소별로 선택하고 더하는 과정
  • gt는 (1) 과거와 현재의 결합 (2) 활성화함수 tanh를 적용으로 나누어지는데 이중에서 (1) 과거와 현재의 정보를 결합하는 과정만 해당한다. 차이점은 요소별 선택 후 덧셈
  • 이러한 결합을 쓰는 이유? 게이트를 이용하여 과거와 현재의 정보를 제어 (일반적인 설명, 솔직히 내가 좋아하는 설명은 아님)

- 느낌4: ct에 대하여 (2) // ct는 왜 과거와 현재의 정보를 제어한다고 볼 수 있는가?

t=1 시점 계산과정관찰

input_gate[1],g[1],forget_gate[1],cell[0]    # g[1]:현재시점 cell[0]:과거시점
(tensor([0.9065, 0.9999], grad_fn=<SelectBackward0>),
 tensor([0.9931, 0.9999], grad_fn=<SelectBackward0>),
 tensor([0.9931, 0.0014], grad_fn=<SelectBackward0>),
 tensor([ 0.3592, -0.9373], grad_fn=<SelectBackward0>))

[0.9,1.0]gt+[1.0,0.0]ct1

  • forget_gate는 ct1의 첫번째 원소는 기억하고, 두번째 원소는 잊으라고 말하고 있음 // forget_gate는 과거(ct1)의 정보를 얼마나 잊을지 (= 얼마나 기억할지) 를 결정한다고 해석할 수 있다.
  • input_gate는 gt의 첫번째 원소와 두번째 원소를 모두 기억하되 두번째 원소를 좀 더 중요하게 기억하라고 말하고 있음 // input_gate는 현재(gt)의 정보를 얼만큼 강하게 반영할지 결정한다.
  • 이 둘을 조합하면 ct가 현재와 과거의 정보중 어떠한 정보를 더 중시하면서 기억할지 결정한다고 볼 수 있다.

이 설명은 제가 좀 싫어해요, 싫어하는 이유는 (1) “기억의 정도를 조절한다”와 “망각의 정도를 조절한다”는 사실 같은말임. 그래서 forget_gate의 용어가 모호함. (2) 기억과 망각을 조정하는 방식으로 꼭 gate의 개념을 사용해야 하는건 아님

- 느낌5: ct에 대하여 (3)

  • 사실상 LSTM 알고리즘의 꽃이라 할 수 있음.
  • LSTM은 long short term memory의 약자임. 기존의 RNN은 장기기억을 활용함에 약점이 있는데 LSTM은 단기기억/장기기억 모두 잘 활용함.
  • LSTM이 장기기억을 잘 활용하는 비법은 바로 ct에 있다.

- 느낌6: ht에 대하여 - 계산방법: tanh(ct)를 요소별로 선택

- RNN, LSTM의 변수들 비교 테이블

과거정보 현재정보 과거와 현재의 결합방식 활성화 느낌 비고
RNN-ht ht1 xt × + tanh 간장
LSTM-gt ht1 xt × + tanh 1차간장
LSTM-ct ct1 gt + None 2차간장 gate를 열림정도를 판단할때 xtht1을 이용
LSTM-ht None ct None tanh, 3차간장 gate를 열림정도를 판단할때 xtht1을 이용
  • RNN은 기억할 과거정보가 ht1 하나이지만 LSTM은 ct1, ht1 2개이다.

- 알고리즘리뷰 :

  • 콩물,과거3차간장 ×,+,tanh 현재1차간장
  • 현재1차간장, 과거2차간장 ,+,tanh 현재2차간장
  • 현재2차간장 tanh, 현재3차간장

LSTM이 강한이유

- LSTM이 장기기억에 유리함. 그 이유는 input, forget, output gate 들이 과거기억을 위한 역할을 하기 때문.

  • 비판: 아키텍처에 대한 이론적 근거는 없음. 장기기억을 위하여 꼭 LSTM같은 구조일 필요는 없음. (왜 3차간장을 만들때 tanh를 써야하는지? 게이트는 꼭3개이어야 하는지?)

- 저는 사실 아까 살펴본 아래의 이유로 이해하고 있습니다.

  • 실험적으로 살펴보니 LSTM이 RNN보다 장기기억에 유리했음.
  • 그 이유: RRN은 ht의 값이 -1 혹은 1로 결정되는 경우가 많았음. 그러나 경우에 따라서는 ht이 -1~1의 값을 가지는 것이 문맥적 뉘앙스를 포착하기에는 유리한데 LSTM이 이러한 방식으로 학습되는 경우가 많았음.
  • 왜 LSTM의 ht은 -1,1 이외의 값을 쉽게 가질 수 있는가? (1) gate들의 역할 (2) 마지막에 취해지는 tanh 때문

참고자료들